// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the SuccessFactors, Inc. nor the names of
//       its contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.jxt;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.xml.sax.Locator;

/**
 * JxtNode
 *
 * @author Jeffrey Ichnowski
 * @version $Revision: 8 $
 */
public abstract class JxtNode {

    private JxtNode _parent;

    private JxtNode _firstChild;
    private JxtNode _lastChild;

    private JxtNode _nextSibling;
    private JxtNode _previousSibling;

    transient Iterable<JxtNode> _childrenIterable;

    private final String _tagName;

    private String _uri;
    private int _line;
    private int _column;

    protected JxtNode(String tagName) {
        _tagName = tagName;
    }

    public final <T extends JxtNode> T appendChild(final T node) {
        if (node._parent != null) {
            node._parent.removeChild(node);
        }

        if (_firstChild == null) {
            _firstChild = node;
        } else {
            node._previousSibling = _lastChild;
            _lastChild._nextSibling = node;
        }

        _lastChild = node;

        node._parent = this;

        assert node._nextSibling == null;

        return node;
    }

    public final JxtNode removeChild(final JxtNode node) {
        if (node._parent != this) {
            throw new IllegalArgumentException("node to be removed is not a child of this node");
        }

        if (node == _firstChild) {
            _firstChild = node._nextSibling;
        } else {
            node._previousSibling._nextSibling = node._nextSibling;
            node._previousSibling = null;
        }

        if (node == _lastChild) {
            _lastChild = node._previousSibling;
        } else {
            node._nextSibling._previousSibling = node._previousSibling;
            node._nextSibling = null;
        }

        node._parent = null;

        return node;
    }

    public final JxtNode getParent() {
        return _parent;
    }

    public final JxtNode getFirstChild() {
        return _firstChild;
    }

    public final JxtNode getLastChild() {
        return _lastChild;
    }

    public final JxtNode getNextSibling() {
        return _nextSibling;
    }

    public final JxtNode getPreviousSibling() {
        return _previousSibling;
    }

    public final Iterable<JxtNode> children() {
        if (_childrenIterable == null) {
            _childrenIterable = new Iterable<JxtNode>() {
                public Iterator<JxtNode> iterator() {
                    return new ChildrenIterator();
                }
            };
        }

        return _childrenIterable;
    }

    public final String getTagName() {
        return _tagName;
    }

    public final void setLocation(Locator loc) {
        _uri = loc.getSystemId();
        _line = loc.getLineNumber();
        _column = loc.getColumnNumber();
    }

    public final void setLocation(String file, int line, int col) {
        _uri = file;
        _line = line;
        _column = col;
    }

    public final String getSystemId() {
        return _uri;
    }

    public final int getLineNumber() {
        return _line;
    }

    public final int getColumnNumber() {
        return _column;
    }

    public JxtExpr toExpr(final ValidationContext vc, String value) {
        final List<JxtExpr> exprs = new ArrayList<JxtExpr>();

        new ExprParser() {
            protected void text(String text) {
                exprs.add(JxtExpr.createLiteral(text));
            }
            protected void expr(String expr) {
                exprs.add(JxtExpr.createCode(expr));
            }
            @Override
            protected void warn(String msg, Object ... args) {
                vc.warn(JxtNode.this, msg, args);
            }
        }.parse(value);

        if (exprs.size() == 0) {
            return JxtExpr.createLiteral("");
        } else if (exprs.size() == 1) {
            return exprs.get(0);
        } else {
            return JxtExpr.createConcat(
                exprs.toArray(new JxtExpr[exprs.size()]));
        }

//         Matcher m = VALUE_EXPR.matcher(value);
//         if (m.matches()) {
//             return JxtExpr.createCode(m.group(1));
//         } else {
//             return JxtExpr.createLiteral(value.replaceAll("\\\\\\$", "\\$"));
//         }
    }

    /** Called by JxtParser to set attributes onto the node. */
    public void setAttribute(ValidationContext ctx, String name, String value) {
        throw new IllegalArgumentException(
            getClass().getName()+" does not accept attributes");
    }

    /** Meant to be overridden by sub-classes. */
    public final void validate(ValidationContext ctx) {
        validateSelf(ctx);

        for (JxtNode c = getFirstChild() ; c != null ; c = c.getNextSibling()) {
            c.validate(ctx);
        }
    }

    protected void validateSelf(ValidationContext ctx) {
    }

    /** Meant to be overridden by sub-classes. */
    public void translate(TranslateContext ctx) {
    }

    /** Renders in the declaration section of the code.  This should
     * rarely need to be overridden. */
    public void translateDecl(TranslateContext ctx) {
        for (JxtNode c = getFirstChild() ; c != null ; c = c.getNextSibling()) {
            c.translateDecl(ctx);
        }
    }

    public final void translateChildren(TranslateContext ctx) {
//         for (JxtNode c = _firstChild ; c != null ; c = c._nextSibling) {
//             c.translate(ctx);
//         }
        translateChildren(ctx, false);
    }

    private static boolean isWhiteSpace(JxtNode node) {
        if (!(node instanceof TextNode)) {
            return false;
        }

        String text = ((TextNode)node).getText();

        return text.trim().length() == 0;
    }

    public final void translateChildren(TranslateContext ctx, boolean trim) {
        JxtNode c = _firstChild;
        JxtNode last = null;

        if (trim) {
            while (c != null && isWhiteSpace(c)) {
                c = c.getNextSibling();
            }

            if (isWhiteSpace(getLastChild())) {
                last = getLastChild();
                while (isWhiteSpace(last.getPreviousSibling())) {
                    last = last.getPreviousSibling();
                }
            }
        }

        for ( ; c != last ; c = c.getNextSibling()) {
            c.translate(ctx);
        }
    }

    class ChildrenIterator implements Iterator<JxtNode> {
        JxtNode _current;
        JxtNode _next;

        ChildrenIterator() {
            _current = null;
            _next = getFirstChild();
        }

        public boolean hasNext() {
            return _next != null;
        }

        public JxtNode next() {
            if (_next == null) {
                throw new NoSuchElementException();
            }
            _current = _next;
            _next = _next.getNextSibling();
            return _current;
        }

        public void remove() {
            if (_current == null) {
                throw new IllegalStateException();
            }
            removeChild(_current);
            _current = null;
        }
    }

} // JxtNode
